import React, { HTMLAttributes, ReactElement, useEffect, useState } from 'react'; import styled from 'styled-components'; import { CSSTransition, TransitionGroup } from 'react-transition-group'; import './index.scss'; import RightIcon from '../icons/right.svg'; export interface CarouselProps extends HTMLAttributes { dot?: boolean; autoPlay?: boolean; callback?: (index: number) => void; children?: React.ReactNode; } // 不知道react的虚拟节点什么类型所以扩充vNode类型来消除ts警告 type VNode = ReactElement & { type: { name: string } }; const CarouselStyled = styled.div` position: relative; overflow: hidden; width: 100%; height: 100%; `; const ActionWrap = styled.div` position: absolute; top: 0; left: 0; width: 100%; height: 100%; `; type SpanWrapProps = { dot: boolean; }; const SpanWrap = styled.div` position: absolute; bottom: 20px; left: 50%; transform: translateX(-50%); display: ${(props: SpanWrapProps) => (props.dot ? 'flex' : 'none')}; gap: 10px; `; type SpanProp = { selfOrder: number; currentOrder: number; }; const SpanRadius = styled.span` width: 10px; height: 10px; border-radius: 50%; cursor: pointer; background-color: ${(props: SpanProp) => props.selfOrder === props.currentOrder ? 'orange' : '#fff'}; `; const CommonDot = styled.div` position: absolute; top: 50%; transform: translateY(-50%); background-color: transparent; cursor: pointer; &:hover { background-color: #fff; } width: 2em; height: 2em; border-radius: 50%; display: flex; justify-content: center; align-items: center; `; const LeftDot = styled(CommonDot)` left: 10px; `; const RightDot = styled(CommonDot)` right: 10px; `; const Carousel: React.FC = (props) => { const { children, autoPlay, callback, dot, ...rest } = props; const [index, setIndex] = useState(1); const kids = children as ReactElement[]; useEffect(() => { const len = kids.length; let time: number; if (autoPlay) { time = window.setInterval(() => { setIndex((state) => { if (state >= len) { state = 1; } else { state += 1; } callback!(state); return state; }); }, 4000); } return () => { window.clearInterval(time); }; }, []); const render = () => { return React.Children.map(children, (child) => { const vNode = child as VNode; if ( React.isValidElement(vNode) && vNode.props.name === 'CarouselItem' && vNode.props.order === index ) { return vNode; } }); }; const createSpan = () => { const dom: ReactElement[] = []; for (let i = 0; i < kids.length; i++) { dom.push(); } return dom; }; const selectedIndex = (e: React.MouseEvent) => { const el = e.target as HTMLSpanElement; if (el.tagName.toLowerCase() === 'span') { const newOrder = parseInt(el.getAttribute('data-order') || '1', 10); setIndex(newOrder); callback!(newOrder); } }; const clickLeftIcon = () => { if (index > 1) { setIndex((state) => { callback!(state - 1); return state - 1; }); } else { callback!(kids.length); setIndex(kids.length); } }; const clickRightIcon = () => { if (index <= kids.length - 1) { setIndex((state) => { callback!(state + 1); return state + 1; }); } else { callback!(1); setIndex(1); } }; return (
{render()}
) => selectedIndex(e)} dot={dot!}> {createSpan()}
); }; Carousel.defaultProps = { dot: true, autoPlay: true, callback: () => {}, children: [] }; export default Carousel;